---
description: 'Learn how to how to manage product categories using the admin REST APIs. Learn how to create, update, and delete categories, and how to manage products in a category.'
addHowToData: true
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# How to Manage Product Categories

In this document, you’ll learn how to manage product categories using the admin REST APIs.

## Overview

Using the product category admin REST APIs, you can manage the categories in your commerce application.

### Scenario

You want to add or use the following admin functionalities:

- Manage categories including list, create, edit, and delete categories.
- Manage products in a category, including adding or removing products from a category.

---

## Prerequisites

### Medusa Components

It is assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started.

### JS Client

This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.

If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client](../../../js-client/overview.md) installed and have [created an instance of the client](../../../js-client/overview.md#configuration).

### Medusa React

This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.

If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).

### Authenticated Admin User

You must be an authenticated admin user before following along with the steps in the tutorial.

You can learn more about [authenticating as an admin user in the API reference](/api/admin/#section/Authentication).

---

## List Categories

You can retrieve available categories by sending a request to the [List Categories](/api/admin#tag/Product-Category/operation/GetProductCategories) endpoint:

<Tabs groupId="request-type" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.admin.productCategories.list()
.then(({ product_categories, limit, offset, count }) => {
  console.log(product_categories.length)
  // display categories
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { useAdminProductCategories } from "medusa-react"
import { ProductCategory } from "@medusajs/medusa"

function Categories() {
  const { 
    product_categories,
    isLoading } = useAdminProductCategories()

  return (
    <div>
      {isLoading && <span>Loading...</span>}
      {product_categories && !product_categories.length && (
        <span>No Categories</span>
      )}
      {product_categories && product_categories.length > 0 && (
        <ul>
          {product_categories.map(
            (category: ProductCategory) => (
              <li key={category.id}>{category.name}</li>
            )
          )}
        </ul>
      )}
    </div>
  )
}

export default Categories
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/admin/product-categories`, {
  credentials: "include",
})
.then((response) => response.json())
.then(({ product_categories, limit, offset, count }) => {
  console.log(product_categories.length)
  // display product categories
})
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl -L -X GET '<BACKEND_URL>/admin/product-categories' \
-H 'Authorization: Bearer <API_TOKEN>'
```

</TabItem>
</Tabs>

This request returns an array of product categories, as well as [pagination fields](/api/admin#section/Pagination).

You can also pass filters and other selection query parameters to the request. Check out the [API reference](/api/admin#tag/Product-Category/operation/GetProductCategories) for more details on available query parameters.

---

## Create a Category

You can create a category by sending a request to the [Create a Category](/api/admin#tag/Product-Category/operation/PostProductCategories) endpoint:

<Tabs groupId="request-type" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.admin.productCategories.create({
  name: "Skinny Jeans",
})
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { useAdminCreateProductCategory } from "medusa-react"

const CreateCategory = () => {
  const createCategory = useAdminCreateProductCategory()
  // ...

  const handleCreate = () => {
    createCategory.mutate({
      name: "Skinny Jeans",
    })
  }

  // ...
}

export default CreateCategory
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```ts
fetch(`<BACKEND_URL>/admin/product-categories`, {
  credentials: "include",
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Skinny Jeans",
  }),
})
.then((response) => response.json())
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl -L -X POST '<BACKEND_URL>/admin/product-categories' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data-raw '{
    "name": "Skinny Jeans",
}'
```

</TabItem>
</Tabs>

This request requires one body parameter `name`, which is the name of the category. The request also accepts the following optional body parameters:

- `handle`: a string that is typically used as a URL path or slug. If you don’t provide a `handle`, it will be the kebab case format of the name.
- `is_internal`: a boolean that indicates whether the category is visible to customers. By default, it’s `false`.
- `is_active`: a boolean that indicates whether the category is active. By default, it’s `true`.
- `parent_category_id`: An ID of another category that this new category should be a child of.

The request returns the newly created product category.

---

## Retrieve a Category

You can retrieve a product category by sending a request to the [Get a Product Category](/api/admin#tag/Product-Category/operation/GetProductCategoriesCategory) endpoint:

<Tabs groupId="request-type" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.admin.productCategories.retrieve(productCategoryId)
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { useAdminProductCategory } from "medusa-react"

const Category = () => {
  const { 
    product_category, 
    isLoading, 
  } = useAdminProductCategory(productCategoryId)
 
  return (
    <div>
      {isLoading && <span>Loading...</span>}
      {product_category && <span>{product_category.name}</span>}
      
    </div>
  )
}

export default Category
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

<!-- eslint-disable max-len -->

```ts
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}`, {
  credentials: "include",
})
.then((response) => response.json())
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl -L -X GET '<BACKEND_URL>/admin/product-categories/<CAT_ID>' \
-H 'Authorization: Bearer <API_TOKEN>'
```

</TabItem>
</Tabs>

This request requires the ID of the category to be passed as a path parameter.

It returns the full object of the product category.

---

## Edit a Category

You can edit a product category by sending a request to the [Update a Product Category](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategory) endpoint:

<Tabs groupId="request-type" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.admin.productCategories.update(productCategoryId, {
    name: "Skinny Jeans",
})
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { useAdminUpdateProductCategory } from "medusa-react"

const UpdateCategory = () => {
  const updateCategory = useAdminUpdateProductCategory(
    productCategoryId
  )
  // ...

  const handleUpdate = () => {
    updateCategory.mutate({
      name: "Skinny Jeans",
    })
  }

  // ...
}

export default UpdateCategory
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

<!-- eslint-disable max-len -->

```ts
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}`, {
    credentials: "include",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Skinny Jeans",
    }),
  })
  .then((response) => response.json())
  .then(({ product_category }) => {
    console.log(product_category.id)
  })
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl -L -X POST '<BACKEND_URL>/admin/product-categories/<CAT_ID>' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data-raw '{
    "name": "Skinny Jeans",
}'
```

</TabItem>
</Tabs>

This request requires the ID of the category to be passed as a path parameter.

In its body, you can pass any of the category’s fields that you want to update. In the code snippet above, you update the name of the category.

You can check the list of accepted fields in the [API reference](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategory).

The request returns the full object of the updated product category.

---

## Manage Products in a Category

:::note

You can manage the categories of each product individually using the [product APIs](/api/admin#tag/Product). This section explores the other approach of managing a product’s category using the product category APIs.

:::

### Add Products to a Category

You can add more than one product to a category using the [Add Products to a Category](/api/admin#tag/Product-Category/operation/PostProductCategoriesCategoryProductsBatch) endpoint:

<Tabs groupId="request-type" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.admin.productCategories.addProducts(productCategoryId, {
  product_ids: [
    {
      id: productId1,
    },
    {
      id: productId2,
    },
  ],
})
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { useAdminAddProductsToCategory } from "medusa-react"

const UpdateProductsInCategory = () => {
  const addProductsToCategory = useAdminAddProductsToCategory(
    productCategoryId
  )
  // ...

  const handleAdd = () => {
    addProductsToCategory.mutate({
      product_ids: [
        {
          id: productId1,
        },
        {
          id: productId2,
        },
      ],
    })
  }

  // ...
}

export default UpdateProductsInCategory
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

<!-- eslint-disable max-len -->

```ts
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}/products/batch`, {
  credentials: "include",
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    product_ids: [
      {
        id: productId1,
      },
      {
        id: productId2,
      },
    ],
  }),
})
.then((response) => response.json())
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl -L -X POST '<BACKEND_URL>/admin/product-categories/<CAT_ID>/products/batch' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data-raw '{
  "product_ids": [
    {
      "id": "productId1"
    },
    {
      "id": "productId2"
    }
  ]
}'
```

</TabItem>
</Tabs>

This request requires the ID of the category to be passed as a path parameter.

In its body, it requires a `product_ids` parameter which is an array of objects. Each object in the array has the property `id`, which is the ID of the product to add. So, each product you want to add you pass it to the array as an object having the property `id`.

The request returns the full object of the product category updated with the new products.

### Remove Products from a Category

You can remove products from a category by sending a request to the [Delete Products](/api/admin#tag/Product-Category/operation/DeleteProductCategoriesCategoryProductsBatch) endpoint:

<Tabs groupId="request-type" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.admin.productCategories.removeProducts(
  productCategoryId,
  {
    product_ids: [
      {
        id: productId1,
      },
      {
        id: productId2,
      },
    ],
  }
)
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { 
  useAdminDeleteProductsFromCategory,
} from "medusa-react"

const DeleteProductsFromCategory = () => {
  const deleteProductsFromCategory = 
    useAdminDeleteProductsFromCategory(productCategoryId)
  // ...

  const handleDelete = () => {
    deleteProductsFromCategory.mutate({
      product_ids: [
        {
          id: productId1,
        },
        {
          id: productId2,
        },
      ],
    })
  }

  // ...
}

export default DeleteProductsFromCategory
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

<!-- eslint-disable max-len -->

```ts
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}/products/batch`, {
  credentials: "include",
  method: "DELETE",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    product_ids: [
      {
        id: productId1,
      },
      {
        id: productId2,
      },
    ],
  }),
})
.then((response) => response.json())
.then(({ product_category }) => {
  console.log(product_category.id)
})
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl -L -X DELETE '<BACKEND_URL>/admin/product-categories/<CAT_ID>/products/batch' \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: application/json' \
--data-raw '{
  "product_ids": [
    {
      "id": "productId1"
    },
    {
      "id": "productId2"
    }
  ]
}'
```

</TabItem>
</Tabs>

This request requires the ID of the category to be passed as a path parameter.

In its body, it requires a `product_ids` parameter which is an array of objects. Each object in the array has the property `id`, which is the ID of the product to remove. So, each product you want to remove you pass it to the array as an object having the property `id`.

The request returns the full object of the product category updated with the its products.

---

## Delete a Category

You can delete a product category by sending a request to the [Delete a Product Category](/api/admin#tag/Product-Category/operation/DeleteProductCategoriesCategory) endpoint:

<Tabs groupId="request-type" wrapperClassName="code-tabs">
<TabItem value="client" label="Medusa JS Client" default>

```ts
medusa.admin.productCategories.delete(productCategoryId)
.then(({ id, object, deleted }) => {
  console.log(id)
})
```

</TabItem>
<TabItem value="medusa-react" label="Medusa React">

```tsx
import { useAdminDeleteProductCategory } from "medusa-react"

const Category = () => {
  const deleteCategory = useAdminDeleteProductCategory(
    productCategoryId
  )
  // ...

  const handleDelete = () => {
    deleteCategory.mutate()
  }

  // ...
}

export default Category
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

<!-- eslint-disable max-len -->

```ts
fetch(`<BACKEND_URL>/admin/product-categories/${productCategoryId}`, {
  credentials: "include",
  method: "DELETE",
})
.then((response) => response.json())
.then(({ id, object, deleted }) => {
  console.log(id)
})
```

</TabItem>
<TabItem value="curl" label="cURL">

```bash
curl -L -X DELETE '<BACKEND_URL>/admin/product-categories/<CAT_ID>' \
-H 'Authorization: Bearer <API_TOKEN>'
```

</TabItem>
</Tabs>

This request requires the ID of the category to be passed as a path parameter.

The request returns the following fields:

- `id`: The ID of the deleted category.
- `object`: The type of object that was deleted. In this case, the value will be `product-category`.
- `deleted`: A boolean value indicating whether the category was deleted.

---

## See Also

- [How to use product categories in a storefront](../store/use-categories.mdx)
